﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using PacManLib;

namespace PacManAI_Hunters
{
    /// <summary>
    /// This Pac-Man AI follows these rules:
    /// - If a ghost is close, run away
    /// - Otherwise head towards the nearest pellet
    /// We run away from ghosts even if they are scared.
    /// </summary>
    public class PacMan : IPacManAI
    {
        #region IPacManAI Members

        /// <summary>
        /// Returns the nickname of this AI.
        /// </summary>
        public string getAIName()
        {
            return "Hungy hippo";
        }

        /// <summary>
        /// Returns the coder's name.
        /// </summary>
        public string getCodersName()
        {
            return "Richard Shepherd";
        }

        /// <summary>
        /// Returns the direction for Pac-Man to go this turn.
        /// </summary>
        public PlayerInfo.Direction getDesiredDirection(Board board)
        {
            Point pacManPosition = board.PacMan.Position;

            // We find the distance to the nearest ghost...
            PlayerInfo nearestGhost = getNearestGhost(board);
            int distanceToNearestGhost = distanceSquared(nearestGhost.Position, pacManPosition);

            // We work out the target tile...
            Point targetTile = new Point();
            bool scared = false;
            if (distanceToNearestGhost <= 8)
            {
                // There is a ghost nearby, so we run away. We aim for a target tile 
                // in the opposite direction from the ghost. (It doesn't matter if 
                // it's not on the board.)
                targetTile = pacManPosition;
                targetTile.X += (pacManPosition.X - nearestGhost.Position.X);
                targetTile.Y += (pacManPosition.Y - nearestGhost.Position.Y);
                scared = true;
            }
            else
            {
                targetTile = getNearestPellet(board);
            }

            // We've now got a target tile, so we head for it...
            return getDirectionToPoint(board, targetTile, scared);
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Returns the tile holding the nearest pellet, or (-1, -1) if 
        /// there are none remaining on the board.
        /// </summary>
        private Point getNearestPellet(Board board)
        {
            Point nearestPellet = new Point(-1, -1);
            int nearestDistance = MAX_DISTANCE;
            Point pacManPosition = board.PacMan.Position;

            for (int x = 0; x < board.Width; ++x)
            {
                for (int y = 0; y < board.Height; ++y)
                {
                    if (board.Items[x, y] != Board.Item.Pellet
                        &&
                        board.Items[x, y] != Board.Item.PowerPellet)
                    {
                        continue;
                    }

                    Point tile = new Point(x, y);
                    int distance = distanceSquared(pacManPosition, tile);
                    if (distance < nearestDistance)
                    {
                        nearestDistance = distance;
                        nearestPellet = tile;
                    }
                }
            }

            return nearestPellet;
        }

        /// <summary>
        /// Returns the direction for Pac-Man to move to travel in the direction
        /// of the target.
        /// </summary>
        private PlayerInfo.Direction getDirectionToPoint(Board board, Point target, bool scared)
        {
            List<MoveInfo> moveInfos = new List<MoveInfo>();
            checkMove(board, target, PlayerInfo.Direction.Up, moveInfos);
            checkMove(board, target, PlayerInfo.Direction.Down, moveInfos);
            checkMove(board, target, PlayerInfo.Direction.Left, moveInfos);
            checkMove(board, target, PlayerInfo.Direction.Right, moveInfos);

            // We find the direction that has the shortest distance to the
            // target, provided it is valid...
            PlayerInfo.Direction direction = PlayerInfo.Direction.NoChange;
            foreach (MoveInfo moveInfo in moveInfos.OrderBy(x => x.Distance))
            {
                if (scared)
                {
                    // We are scared, so we return the direction
                    // with the shortest distance...
                    direction = moveInfo.Direction;
                    break;
                }
                else
                {
                    // We are not scared, so we check that the direction 
                    // is not the opposite direction to the one we are
                    // currently heading...
                    if (PlayerInfo.oppositeDirection(moveInfo.Direction) != m_previousDirection)
                    {
                        // This direction is not opposite to the way we are heading...
                        direction = moveInfo.Direction;
                        break;
                    }
                }
            }

            m_previousDirection = direction;
            return direction;
        }

        /// <summary>
        /// If the move passed in is valid, we work out the distance to the target
        /// and add it to the collection of move-infos passed in.
        /// </summary>
        private void checkMove(Board board, Point target, PlayerInfo.Direction direction, List<MoveInfo> moveInfos)
        {
            int distance = getDistanceToPoint(board, target, direction);
            if (distance != -1)
            {
                moveInfos.Add(new MoveInfo { Direction = direction, Distance = distance });
            }
        }

        /// <summary>
        /// Returns the distance to the target if Pac-Man moves in the direction
        /// passed in. Returns -1 if the direction would be an illegal move.
        /// </summary>
        private int getDistanceToPoint(Board board, Point target, PlayerInfo.Direction direction)
        {
            Point tile = new Point();
            if (isLegalMove(board, direction, ref tile))
            {
                return distanceSquared(tile, target);
            }
            else
            {
                return -1;
            }
        }

        /// <summary>
        /// True if Pac-Man can move in the direction passed in. If so, the tile
        /// is also filled in.
        /// </summary>
        private bool isLegalMove(Board board, PlayerInfo.Direction direction, ref Point tile)
        {
            tile = board.PacMan.Position;
            switch (direction)
            {
                case PlayerInfo.Direction.Up:
                    tile.Y--;
                    break;
                case PlayerInfo.Direction.Down:
                    tile.Y++;
                    break;
                case PlayerInfo.Direction.Left:
                    tile.X--;
                    break;
                case PlayerInfo.Direction.Right:
                    tile.X++;
                    break;
            }

            // Is the tile on the board?
            if (tile.X < 0) return false;
            if (tile.X >= board.Width) return false;
            if (tile.Y < 0) return false;
            if (tile.Y >= board.Height) return false;

            // Is the tile a wall?
            if (board.Items[tile.X, tile.Y] == Board.Item.Wall) return false;

            return true;
        }

        /// <summary>
        /// Returns the nearest ghost.
        /// </summary>
        private PlayerInfo getNearestGhost(Board board)
        {
            PlayerInfo nearestGhost = null;
            int nearestDistance = MAX_DISTANCE;

            Point pacManPosition = board.PacMan.Position;
            foreach (PlayerInfo ghost in board.Ghosts)
            {
                int distance = distanceSquared(pacManPosition, ghost.Position);
                if (distance < nearestDistance)
                {
                    nearestDistance = distance;
                    nearestGhost = ghost;
                }
            }

            return nearestGhost;
        }

        /// <summary>
        /// Returns the distance-squared between two points.
        /// </summary>
        private int distanceSquared(Point a, Point b)
        {
            int diffX = b.X - a.X;
            int diffY = b.Y - a.Y;
            return diffX * diffX + diffY * diffY;
        }

        #endregion

        #region Private data

        // A distance larger than any board distance squared...
        private const int MAX_DISTANCE = 100000;

        // The previous direction. For this algorithm, Pac-Man does
        // not reverse direction, except when he's scared. Otherwise he 
        // can end up oscillating between two tiles.
        private PlayerInfo.Direction m_previousDirection = PlayerInfo.Direction.NoChange;

        // Info about one direction Pac-Man can take and the
        // distance (squared) to the target...
        private class MoveInfo
        {
            public PlayerInfo.Direction Direction { get; set; }
            public int Distance { get; set; }
        }

        #endregion

    }
}
